package com.fe.class04;

import java.util.Arrays;

/**
 * @Description 在一个数组中，一个数左边比它小的数的总和，叫数的小和，所有数的小和累加起来，叫数组小和。求数组小和
 * @Author sosuke :-)
 * @Date 2021/12/15 06:55
 */
public class Code03_SmallSum {

    public static int smallSum(int[] arr) {
        if (null == arr || arr.length < 2) {
            return 0;
        }
        return process(arr, 0, arr.length - 1);
    }

    /**
     * 求小和问题，可转化成计算每个数右侧有多少个比他大的数，比如num右侧有3个数比他大，则小和需要加上 3*num
     */
    private static int process(int[] arr, int L, int R) {
        if (L == R) return 0;
        int M = L + ((R - L) >> 1);
        int leftSmallSum = process(arr, L, M);//左组产生的小和
        int rightSmallSum = process(arr, M + 1, R);//右组产生的小和
        // 求：左组中每个数在当前右组范围上有多少个比他大的数
        int mergeSmallSum = merge(arr, L, M, R);//合并左右组产生的小和
        return leftSmallSum + rightSmallSum + mergeSmallSum;
    }

    /**
     * 求：左组中每个数在当前右组范围上有多少个比他大的数
     * 谁小拷贝谁，arr[p1] < arr[p2]，则表示arr[p2...R]都是大于arr[p1]的
     * 左组小, 拷贝左组, 产生小和, 看右组有多少个比左组当前大
     * 右组拷贝不产生小和
     * 相等时候先拷贝右组 ==> 一定要在右边找到一个大的才能结算，如果先拷贝左边的就求不出来了
     * @return
     */
    private static int merge(int[] arr, int L, int M, int R) {
        int[] help = new int[R - L + 1];
        int i = 0;
        int p1 = L;
        int p2 = M + 1;
        int res = 0;//小和
        // 左右组合并，求每个左组元素，在右组中有多少个比他大的，产生小和
        while (p1 <= M && p2 <= R) {
            if (arr[p1] < arr[p2]) {
                res += (R - p2 + 1) * arr[p1];
                help[i++] = arr[p1++];
            } else { // 左组大于等于右组，右组拷贝不产生小和，因为要求的是右侧有多少个比arr[p1]大
                help[i++] = arr[p2++];
            }
        }
        while (p1 <= M) {
            help[i++] = arr[p1++];
        }
        while (p2 <= R) {
            help[i++] = arr[p2++];
        }
        System.arraycopy(help, 0, arr, L, help.length);
        return res;
    }

    public static void main(String[] args) {
        int testTime = 500000;
        int maxSize = 5;
        int maxValue = 100;
        for (int i = 0; i < testTime; i++) {
            int[] arr1 = generateRandomArray(maxSize, maxValue);
            int[] arr2 = Arrays.copyOf(arr1, arr1.length);
            int res1 = smallSum(arr1);
            int res2 = comparator(arr2);
            if (res1 != res2) {
                System.out.println("Fuck");
                System.out.println(Arrays.toString(arr1));
                System.out.println("res1 = " + res1);
                System.out.println("res2 = " + res2);
                return;
            }
        }
        System.out.println("Nice");
        int[] arr = generateRandomArray(maxSize, maxValue);
        System.out.println(Arrays.toString(arr));
        int res = smallSum(arr);
        System.out.println("res = " + res);
    }

    // for循环方式求小和
    public static int comparator(int[] arr) {
        if (arr == null || arr.length < 2) {
            return 0;
        }
        int res = 0;
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < i; j++) {
                res += arr[j] < arr[i] ? arr[j] : 0;
            }
        }
        return res;
    }

    // for test
    public static int[] generateRandomArray(int maxSize, int maxValue) {
        int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) ((maxValue + 1) * Math.random());
        }
        return arr;
    }
}
